package com.netx.generics.io;
import java.net.URL;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.net.MalformedURLException;

import com.netx.generics.basic.Checker;
import com.netx.generics.basic.IntegrityException;
import com.netx.generics.util.Strings;
import com.netx.generics.util.Arrays;


public class Location {

	// TYPE:
	private static String[] _protocols = new String[] {"file", "ftp", "http"};
	
	private static URL _getURL(String url) {
		try {
			if(!url.contains("://")) {
				url = "file://"+url;
			}
			// Parse URL path:
			boolean found = false;
			for(String p : _protocols) {
				if(url.startsWith(p)) {
					found = true;
					break;
				}
			}
			if(!found) {
				throw new IllegalArgumentException("unknown protocol: "+url.substring(0, url.indexOf("://")));
			}
			else {
				return new URL(url);
			}
		}
		catch(MalformedURLException mue) {
			throw new IllegalArgumentException("malformed path: "+mue.getMessage());
		}
	}

	// INSTANCE:
	public enum PROTOCOL {FILE, FTP, HTTP};

	private final PROTOCOL _protocol;
	private final String _host;
	private final String _path;
	private final String _username;
	private final String _password;
	
	public Location(PROTOCOL protocol, String host, String path, String username, String password) {
		Checker.checkNull(protocol, "protocol");
		// TODO check syntax for path
		Checker.checkEmpty(path, "path");
		_checkPath(path);
		_protocol = protocol;
		_host = _getHost(host);
		_path = _getPath(path.replaceAll("\\", "/"));
		_username = username;
		_password = password;
	}
	
	public Location(PROTOCOL protocol, String host, String path) {
		this(protocol, host, path, null, null);
	}

	public Location(URL url, String username, String password) {
		Checker.checkNull(url, "url");
		// Check URL protocol:
		if(Arrays.find(url.getProtocol(), _protocols) == -1) {
			throw new IllegalArgumentException("unknown protocol: "+url.getProtocol());
		}
		_protocol = Enum.valueOf(PROTOCOL.class, url.getProtocol().toUpperCase());
		if(_protocol == PROTOCOL.FILE) {
			// Parse the file name:
			// Windows UNC path:
			// (no need to check for "\\", because URL translates to "/")
			String path = url.getPath();
			if(path.startsWith("//")) {
				if(Strings.countOccurrences(path, "/") == 2) {
					// Just the host name:
					_host = _getHost(path.substring(2));
					_path = "/";
				}
				else {
					_host = _getHost(path.substring(2, path.indexOf("/", 3)));
					_path = _getPath(path.substring(path.indexOf("/", 3)));
				}
			}
			// Local path:
			else {
				// Cannot use URL.getPath, because it removes the drive specifier:
				_path = _getPath(url.toString().substring("file://".length()));
				_host = null;
			}
		}
		else {
			_host = _getHost(url.getHost());
			_path = _getPath(url.getPath());
		}
		_checkPath(_path);
		_username = username;
		_password = password;
	}

	public Location(URL url) {
		this(url, null, null);
	}

	public Location(java.io.File file) {
		this(_getURL(Checker.checkNull(file, "file").toString()));
	}

	public Location(String descriptor) {
		this(_getURL(Checker.checkNull(descriptor, "descriptor").toString()));
	}
	
	public PROTOCOL getProtocol() {
		return _protocol;
	}
	
	public String getHost() {
		try {
			return _host==null ? InetAddress.getLocalHost().getHostName() : _host;
		}
		catch(UnknownHostException uhe) {
			throw new IntegrityException(uhe);
		}
	}

	public String getPath() {
		if(_path.contains(":")) {
			return _path;
		}
		else if(_path.equals("/")) {
			return _path;
		}
		else {
			return "/"+_path;
		}
	}

	public String getUsername() {
		return _username;
	}

	public String getPassword() {
		return _password;
	}

	public String getPhysicalPath() {
		if(_protocol == PROTOCOL.FILE) {
			if(_runningOnWindows()) {
				if(_host != null) {
					// UNC path:
					return "//"+getHost()+getPath();
				}
				else {
					if(!_path.contains(":")) {
						// No drive specifier, UNC path to localhost:
						return "//"+getHost()+getPath();
					}
					else {
						// Regular local file:
						return getPath();
					}
				}
			}
			else {
				//TODO support UNIX filenames
				throw new IntegrityException();
			}
		}
		else {
			return null;
		}
	}

	public boolean isLocal() {
		return _host == null;
	}
	
	public URL toURL() {
		try {
			if(_protocol == PROTOCOL.FILE) {
				return new URL("file://"+getPhysicalPath());
			}
			else {
				return new URL(_protocol.toString().toLowerCase()+"://"+getHost()+getPath());
			}
		}
		catch(MalformedURLException mue) {
			throw new IllegalArgumentException(mue);
		}
	}
	
	public String toString() {
		return toURL().toString();
	}
	
	private String _getHost(String host) {
		if(Strings.isEmpty(host) || host.equals("localhost")) {
			return null;
		}
		else {
			try {
				InetAddress localhost = InetAddress.getLocalHost();
				if(host.equals(localhost.getHostAddress()) || host.equals(localhost.getHostName())) {
					return null;
				}
				else {
					return host;
				}
			}
			catch(UnknownHostException uhe) {
				throw new IntegrityException(uhe);
			}
		}
	}
	
	private String _getPath(String path) {
		if(path.charAt(0) == '/') {
			path = path.substring(1);
		}
		if(path.charAt(path.length()-1) == '/') {
			path = path.substring(0, path.length()-1);
		}
		return path;
	}
	
	private void _checkPath(String path) {
		if(path.contains("..")) {
			throw new IllegalArgumentException("path cannot have '..' references");
		}
	}
	
	private boolean _runningOnWindows() {
		return java.io.File.separatorChar == '\\';
	}
}
